Skip to content

Recoil 状态管理

在大型项目中使用 Recoil 管理全局状态是一个非常实用的方案,特别适用于状态分散但组件结构清晰的场景,比如一个具有多个视图、筛选、编辑功能的 Todo List 应用。下面我将结合一个中大型的 Todo List 项目示例,从状态设计到操作方法,系统地说明如何使用 Recoil。


✅ Recoil 核心概念回顾

概念说明
atom原子状态,是全局状态的最小单位。可被多个组件共享。
selector派生状态,可以从一个或多个 atom 派生,具有缓存能力,支持读写。
useRecoilState读取 + 设置 atom
useRecoilValue只读取 atom 或 selector
useSetRecoilState只设置 atom
RecoilRoot包裹在应用最外层,提供上下文环境

🎯 示例场景:Todo List 项目功能模块

功能包括:

  1. 添加 / 删除 / 更新 todo
  2. 标记完成状态
  3. 筛选(全部、完成、未完成)
  4. 编辑功能
  5. 多视图支持(如日历视图、列表视图)

🧱 状态设计(atoms)

ts
// atoms/todoListState.ts
import { atom } from 'recoil';

export type Todo = {
    id: string;
    title: string;
    completed: boolean;
    createdAt: number;
};

export const todoListState = atom<Todo[]>({
    key: 'todoListState',
    default: []
});

export const todoFilterState = atom<'all' | 'completed' | 'uncompleted'>({
    key: 'todoFilterState',
    default: 'all'
});

🧮 派生状态(selectors)

ts
// selectors/filteredTodoList.ts
import { selector } from 'recoil';
import { todoListState, todoFilterState } from '../atoms/todoListState';

export const filteredTodoListState = selector({
    key: 'filteredTodoListState',
    get: ({ get }) => {
        const filter = get(todoFilterState);
        const list = get(todoListState);

        switch (filter) {
            case 'completed':
                return list.filter((item) => item.completed);
            case 'uncompleted':
                return list.filter((item) => !item.completed);
            default:
                return list;
        }
    }
});

🧩 使用状态的组件

添加 Todo

tsx
import { useSetRecoilState } from 'recoil';
import { todoListState } from '../atoms/todoListState';
import { v4 as uuidv4 } from 'uuid';

export const AddTodo = () => {
    const setTodoList = useSetRecoilState(todoListState);

    const addTodo = (title: string) => {
        setTodoList((oldList) => [...oldList, { id: uuidv4(), title, completed: false, createdAt: Date.now() }]);
    };

    return (
        <input
            onKeyDown={(e) => {
                if (e.key === 'Enter' && e.currentTarget.value.trim()) {
                    addTodo(e.currentTarget.value.trim());
                    e.currentTarget.value = '';
                }
            }}
            placeholder="Add todo"
        />
    );
};

显示 Todo 列表

tsx
import { useRecoilValue } from 'recoil';
import { filteredTodoListState } from '../selectors/filteredTodoList';

export const TodoList = () => {
    const todos = useRecoilValue(filteredTodoListState);

    return (
        <ul>
            {todos.map((todo) => (
                <li key={todo.id}>
                    {todo.title} {todo.completed ? '(Done)' : ''}
                </li>
            ))}
        </ul>
    );
};

切换 Filter

tsx
import { useRecoilState } from 'recoil';
import { todoFilterState } from '../atoms/todoListState';

export const FilterControl = () => {
    const [filter, setFilter] = useRecoilState(todoFilterState);

    return (
        <div>
            <button onClick={() => setFilter('all')} disabled={filter === 'all'}>
                全部
            </button>
            <button onClick={() => setFilter('completed')} disabled={filter === 'completed'}>
                已完成
            </button>
            <button onClick={() => setFilter('uncompleted')} disabled={filter === 'uncompleted'}>
                未完成
            </button>
        </div>
    );
};

📦 项目结构建议

src/
├── atoms/                // 所有 atom 状态定义
│   └── todoListState.ts
├── selectors/            // 所有 selector 状态派生逻辑
│   └── filteredTodoList.ts
├── components/           // UI 组件
│   ├── AddTodo.tsx
│   ├── TodoList.tsx
│   └── FilterControl.tsx
├── App.tsx
└── index.tsx

🚀 高级用法(适用于大型项目)

1. 使用 atomFamily 区分多个 Todo 模块(比如分组)

ts
import { atomFamily } from 'recoil';

export const todoListFamily = atomFamily<Todo[], string>({
    key: 'todoListFamily',
    default: []
});

这样就可以用 useRecoilState(todoListFamily('work')) 管理不同组的任务。


2. 异步状态加载(与 selector 结合)

ts
export const todoListAsyncState = selector({
    key: 'todoListAsyncState',
    get: async () => {
        const response = await fetch('/api/todos');
        return await response.json();
    }
});

3. 封装自定义 hook 提高可维护性

ts
export const useTodoActions = () => {
    const setTodos = useSetRecoilState(todoListState);

    const toggleTodo = (id: string) =>
        setTodos((todos) => todos.map((t) => (t.id === id ? { ...t, completed: !t.completed } : t)));

    const deleteTodo = (id: string) => setTodos((todos) => todos.filter((t) => t.id !== id));

    return { toggleTodo, deleteTodo };
};

✅ 结语

在大型项目中,使用 Recoil 具有以下优势:

  • 状态管理简单直观(尤其在组件粒度状态较多时)
  • 支持异步和派生状态,适合复杂计算
  • 可以和 React 并发特性天然兼容
  • 结合 atomFamilyselectorFamily 支持模块化设计